#include "YUVOpenGLWidget.h"
#include "fvideodecode.h"
#include "RTSP.h"

#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QDebug>

extern "C" {
#include <libavutil/frame.h>
#include <libavutil/pixfmt.h>
}

#define VERTEXIN 0
#define TEXTUREIN 1

YUVOpenGLWidget::YUVOpenGLWidget(QWidget *parent)
    : QOpenGLWidget(parent)
    , m_rtsp(new RTSP(this))
    , m_videoDecode(new FVideoDecode(this))
{
    vertices[0] = -1.0f;
    vertices[1] = -1.0f;
    vertices[2] = -1.0f;
    vertices[3] = +1.0f;
    vertices[4] = +1.0f;
    vertices[5] = +1.0f;
    vertices[6] = +1.0f;
    vertices[7] = -1.0f;
    vertices[8] = 0.0f;
    vertices[9] = 1.0f;
    vertices[10] = 0.0f;
    vertices[11] = 0.0f;
    vertices[12] = 1.0f;
    vertices[13] = 0.0f;
    vertices[14] = 1.0f;
    vertices[15] = 1.0f;

    QObject::connect(m_videoDecode, SIGNAL(sigYuv(AVFrame *)), this, SLOT(slotShowYuv(AVFrame *)));

    m_rtsp->setVideoDecode(m_videoDecode);
    m_rtsp->start();
}

YUVOpenGLWidget::~YUVOpenGLWidget()
{
    makeCurrent();
    vbo.destroy();
    textureY->destroy();
    textureU->destroy();
    textureV->destroy();
    doneCurrent();
}

void YUVOpenGLWidget::slotShowYuv(AVFrame *pAVFrame)
{
    if (frame)
        av_frame_free(&frame);
    frame = pAVFrame;
    if (videoW != pAVFrame->width || videoH != pAVFrame->height) {
        videoW = pAVFrame->width;
        videoH = pAVFrame->height;
        resizeGL(this->width(), this->height());
    }

    update();
}

void YUVOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    updateVertBuffer();

    //顶点着色器
    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    if (!vshader->compileSourceFile("/home/yang/video-player/yuv420p.vert")) {
        qWarning() << "yuv420p.vert complie failed!";
    }
    //片段着色器
    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    if (!fshader->compileSourceFile("/home/yang/video-player/yuv420p.frag")) {
        qWarning() << "yuv420p.frag complie failed!";
    }

    program = new QOpenGLShaderProgram(this);
    program->addShader(vshader);
    program->addShader(fshader);
    program->bindAttributeLocation("vertexIn", VERTEXIN);
    program->bindAttributeLocation("textureIn", TEXTUREIN);
    program->link();
    program->bind();
    program->enableAttributeArray(VERTEXIN);
    program->enableAttributeArray(TEXTUREIN);
    program->setAttributeBuffer(VERTEXIN, GL_FLOAT, 0, 2, 2 * sizeof(GLfloat));
    program->setAttributeBuffer(TEXTUREIN, GL_FLOAT, 8 * sizeof(GLfloat), 2, 2 * sizeof(GLfloat));

    textureUniformY = program->uniformLocation("tex_y");
    textureUniformU = program->uniformLocation("tex_u");
    textureUniformV = program->uniformLocation("tex_v");
    //创建纹理
    textureY = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureU = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureV = new QOpenGLTexture(QOpenGLTexture::Target2D);
    textureY->create();
    textureU->create();
    textureV->create();
    idY = textureY->textureId();
    idU = textureU->textureId();
    idV = textureV->textureId();

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glBindTexture(GL_TEXTURE_2D, idY);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D, idU);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D, idV);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    //指定y纹理要使用新值
    glUniform1i(textureUniformY, 0);
    //指定u纹理要使用新值
    glUniform1i(textureUniformU, 1);
    //指定v纹理要使用新值
    glUniform1i(textureUniformV, 2);

    glClearColor(0.0, 0.0, 0.0, 0.0);
}

void YUVOpenGLWidget::resizeGL(int w, int h)
{
    if (!frame) {
        return;
    }

    qreal video_src = videoW * 1.0 / videoH;
    qreal video_des = w * 1.0 / h;
    qreal new_h = video_des / video_src;

    if (video_src < video_des) {
        new_h = 1.0 / new_h;
        vertices[0] = -new_h;
        vertices[1] = -1.0f;
        vertices[2] = -new_h;
        vertices[3] = +1.0f;
        vertices[4] = +new_h;
        vertices[5] = +1.0f;
        vertices[6] = +new_h;
        vertices[7] = -1.0f;
    } else {
        vertices[0] = -1.0f;
        vertices[1] = -new_h;
        vertices[2] = -1.0f;
        vertices[3] = +new_h;
        vertices[4] = +1.0f;
        vertices[5] = +new_h;
        vertices[6] = +1.0f;
        vertices[7] = -new_h;
    }

    updateVertBuffer();
}

void YUVOpenGLWidget::paintGL()
{
    if (!frame) {
        return;
    }
    int offset = 0;
    switch (frame->format) {
    case AV_PIX_FMT_YUV444P:
        offset = 0;
        break;
    case AV_PIX_FMT_YUV420P:
        offset = 1;
        break;
    }
    glActiveTexture(GL_TEXTURE0); //激活纹理单元GL_TEXTURE0
    glBindTexture(GL_TEXTURE_2D, idY); //绑定y分量纹理对象id到激活的纹理单元
    //使用内存中的数据创建真正的y分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->linesize[0], frame->height, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[0]);

    glActiveTexture(GL_TEXTURE1); //激活纹理单元GL_TEXTURE1
    glBindTexture(GL_TEXTURE_2D, idU);
    //使用内存中的数据创建真正的u分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->linesize[1], frame->height >> offset, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[1]);

    glActiveTexture(GL_TEXTURE2); //激活纹理单元GL_TEXTURE2
    glBindTexture(GL_TEXTURE_2D, idV);
    //使用内存中的数据创建真正的v分量纹理数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->linesize[2], frame->height >> offset, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[2]);

    //使用顶点数组方式绘制图形
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}

void YUVOpenGLWidget::updateVertBuffer()
{
    if (!vbo.isCreated()) {
        vbo.create();
        vbo.bind();
    }

    vbo.allocate(vertices, sizeof(vertices));
}
